﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using NiuX.LightServer.Extensions;
using NiuX.LightServer.Handlers;
using NiuX.LightServer.Models;
using NiuX.LightServer.Options;

namespace NiuX.LightServer
{
    public class Server : IServer
    {
        private static readonly object _locker = new object();
        private readonly List<IHttpHandler> _handlers = new List<IHttpHandler>();
        private readonly ConcurrentDictionary<string, IHttpHandler> _handlersCache = new ConcurrentDictionary<string, IHttpHandler>();
        private string[] _urls = { };
        private int _port = 5000;
        private string _ip = "localhost";
        private CorsOptions _corsOption;

        public IWebHost Host { get; private set; }

        public IServer SetPort(int port)
        {
            _port = port;

            return this;
        }

        public IServer SetIP(string ip)
        {
            if (!string.IsNullOrEmpty(ip))
            {
                _ip = ip;
            }

            return this;
        }

        public IServer EnableCors()
        {
            _corsOption = new CorsOptions()
            {
                IsAllowAll = true
            };

            return this;
        }

        public IServer EnableCors(CorsOptions option)
        {
            _corsOption = option;

            return this;
        }

        public async Task RunAsync()
        {
            string url = $"http://{_ip}:{_port}";
            var urls = new List<string>();
            if (!_urls.Any())
            {
                urls.Add(url);
            }
            else
            {
                urls = _urls.ToList();
            }

            Host =
                 new WebHostBuilder()
                    .UseUrls(urls.ToArray())
                    .UseKestrel()
                    .Configure(app =>
                    {
                        app.Run(async http =>
                        {
                            var req = http.Request;
                            var resp = http.Response;
                            var method = http.Request.Method;
                            var path = req.Path;

                            var cacheKey = $"Request:{method}-{path}";

                            try
                            {
                                await CorsHandler.Handle(_corsOption, http);
                            }
                            catch (CorsException e)
                            {
                                await resp.WriteAsync("跨域了哦");
                                return;
                            }

                            _handlersCache.TryGetValue(cacheKey, out var handler);

                            if (handler == null)
                            {
                                handler = _handlers.FirstOrDefault(h => h.Method == method && PathUtil.IsMatch(path, h.Path));

                                if (handler != null)
                                {
                                    _handlersCache.TryAdd(cacheKey, handler);
                                }
                            }

                            if (handler != null)
                            {
                                try
                                {
                                    await handler.Handler(new Request(req, handler.Path), new Response(resp));
                                    return;
                                }
                                catch (Exception e)
                                {
                                    resp.StatusCode = (int)HttpStatusCode.InternalServerError;
                                    await resp.WriteAsync("InternalServerError");
                                    return;
                                }
                            }

                            resp.StatusCode = (int)HttpStatusCode.NotFound;

                            await resp.WriteAsync("NotFound");
                        });
                    })
                    .Build();
            await Host.StartAsync();

            urls.ForEach(u =>
            {
                Console.WriteLine($"NiuX.LightServer listen on {u} .");
            });

        }

        public IServer AddHandler(HttpHandler handler)
        {
            if (handler == null)
            {
                throw new ArgumentNullException(nameof(handler));
            }
            if (string.IsNullOrEmpty(handler.Method))
            {
                throw new ArgumentNullException("handler.Method");
            }
            if (string.IsNullOrEmpty(handler.Path))
            {
                throw new ArgumentNullException("handler.Path");
            }
            if (handler.Handler == null)
            {
                throw new ArgumentNullException("handler.Handle");
            }

            if (_handlers.Any(h => h.Path.Equals(handler.Path, StringComparison.CurrentCultureIgnoreCase) &&
                                   h.Method == handler.Method))
            {
                throw new Exception($"{handler.Method} {handler.Path} only can be set 1 handler");
            }

            lock (_locker)
            {
                _handlers.Add(handler);
            }

            return this;
        }

        public Task Stop()
        {
            return Host?.StopAsync();
        }

        /// <summary>
        /// 如果设置了listen urls则ip，port不起作用。
        /// </summary>
        /// <param name="urls"></param>
        /// <returns></returns>
        public IServer SetLinstenUrls(params string[] urls)
        {
            this._urls = urls ?? throw new ArgumentNullException("urls");
            return this;
        }
    }
}
